Skip to content

Kit refresh: Expo SDK 55, latest deps, fixes of native flows#13

Merged
Gbuomprisco merged 5 commits into
makerkit:mainfrom
ChcJohnie:feat/expo-sdk-v55
May 21, 2026
Merged

Kit refresh: Expo SDK 55, latest deps, fixes of native flows#13
Gbuomprisco merged 5 commits into
makerkit:mainfrom
ChcJohnie:feat/expo-sdk-v55

Conversation

@ChcJohnie

@ChcJohnie ChcJohnie commented May 18, 2026

Copy link
Copy Markdown
Contributor

Changes

Upgrades

  • Expo SDK 52 → 55, React 19, Reanimated 4 (adds react-native-worklets peer), explicit @expo/metro-runtime peer
  • React Native Reusables component resync — 27 existing + 5 new (icon, aspect-ratio, context-menu, tooltip, native-only-animated-view). Kit-extensions retained where they suit the kit better (centered-card Alert,
    mobile-first native:* sizing on interactive components, gap-based ToggleGroup)
  • Catalog deps refreshed (@supabase/*, @tanstack/react-query, @rn-primitives/*, tailwind-merge, sonner-native, etc.). Policy: mirror Expo's bundledNativeModules.json for Expo-managed deps, caret-range everything
    else

Reworked flows

  • Profile picture upload — expo-document-picker (web-only APIs) → expo-image-picker + expo-image-manipulator (resize 512px, JPEG q=0.8). Single hook handles upload + clear
  • Form validation — new onValidationErrors helper in @kit/ui surfaces the first zod error as a toast across all 6 RHF forms (was previously silent). Friendlier zod messages on auth + account schemas

Bug fixes

Not verified whether these were present in the old version or only surfaced after the deps upgrade — flagging in case any are already known.

  • Theme toggle icons (greenish on iOS, invisible in dark mode) — routed through new Icon wrapper
  • Tabs selected state invisible in light mode
  • Input vertical alignment + placeholder opacity on native
  • RHF Controller {...field} spread was eating keystrokes on RN TextInput — switched to explicit value / onBlur / onChangeText
  • Android emulator Supabase URL rewrite (localhost10.0.2.2) so local Supabase is reachable from the emulator
  • Sonner-native Toaster configured (bottom-center, swipe-left, system theme)

Cleanup

  • Removed unused deps: @vercel/style-guide, sonner (web; sonner-native covers web too), expo-document-picker, stale apps/expo-app/package-lock.json
  • Deprecated typography.tsx removed; H1Muted now shorthand exports on text.tsx (no caller changes)

Notes for update

  • Upgrade path untested. The old version did not start at all with current build tools and the versions were majorly diverged, so I went forward-only rather than walking an incremental upgrade. Public API should remain
    as before — no intentional breaking changes for kit consumers.
  • New Expo native modules (expo-image-picker, expo-image-manipulator, expo-crypto) — consumers need prebuild --clean after upgrading

Tested

  • iOS simulator + Android emulator: sign-in / sign-up / password-reset flows
  • Profile picture upload, clear, re-upload on both platforms
  • Account forms (name / email / password) — happy path + validation errors surfaced as toasts
  • Theme toggle: light / dark / system on both platforms
  • pnpm expo:doctor passes

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced form validation with clearer, contextual error messages for sign-up, sign-in, and password reset flows.
    • Improved profile picture management with built-in image cropping and compression.
    • Added theme toggle with automatic dark/light mode support across the app.
    • Platform-aware UI components with optimized interactions for mobile and web.
  • Bug Fixes

    • Fixed validation error display for email and password fields.
    • Corrected password confirmation messaging in account settings.
  • Improvements

    • Enhanced settings interface with improved icons and navigation.
    • Better toast notifications for user feedback.

Review Change Stack

ChcJohnie added 5 commits May 18, 2026 10:48
- expo sdk v55
- few commands added for dep management
- fix android simulator supabase connection
- fix node types issues
Adds components.json + update to latest RNR kit components.
Misc: subtasks
- doc: comment on kit overwrites (keeping custom styles)
- fix: dark mode breakeges
- fix: use button link correctly
- fix: state errors on account page
- fix: let TS handle no undef instead of eslint
- fix: theme toggling state
- fix: render validation errors correctly
- non native skeleton
- missing package declaration
- optimistic profile picture set
- toaster with correct theme support
@coderabbitai

coderabbitai Bot commented May 18, 2026

Copy link
Copy Markdown

Walkthrough

Refactors the UI kit to function components with shared primitives and updated styling, updates account/auth forms and hooks (including avatar flow), adjusts app screens/configs, rewrites Supabase helper types and Android client URL handling, adds a Supabase seed, and updates workspace/tooling configurations.

Changes

UI Kit overhaul and new primitives

Layer / File(s) Summary
Foundations: shadcn config, deps, primitives, text, and barrel
packages/ui/components.json, packages/ui/package.json, packages/ui/src/components/ui/icon.tsx, packages/ui/src/components/ui/native-only-animated-view.tsx, packages/ui/src/components/ui/text.tsx, packages/ui/src/index.ts
Adds shadcn config, updates deps, introduces Icon and NativeOnlyAnimatedView, overhauls Text with variants/ARIA, and expands barrel exports.
Buttons, badge, input, label, textarea
packages/ui/src/components/ui/button.tsx, .../badge.tsx, .../input.tsx, .../label.tsx, .../textarea.tsx
Adds ButtonLink and updates Button variants; rewrites Badge/Input/Label/Textarea with platform-aware classes and simplified props.
Form controls: checkbox, radio, select, switch, progress
packages/ui/src/components/ui/{checkbox,radio-group,select,switch,progress}.tsx
Refactors controls to function components, unifies Switch, rebuilds Select overlay/animations, and splits Progress indicator logic per platform.
Overlays and popups: alert-dialog, dialog, popover, tooltip, hover-card
packages/ui/src/components/ui/{alert-dialog,dialog,popover,tooltip,hover-card}.tsx
Unifies overlay handling with FullWindowOverlay/NativeOnlyAnimatedView and simplifies animation classes and APIs.
Menus and navigation: dropdown, context-menu, menubar, navigation-menu, tabs, accordion
packages/ui/src/components/ui/{dropdown-menu,context-menu,menubar,navigation-menu,tabs,accordion}.tsx
Refactors menu/navigation components to function forms, adds context-menu wrappers, and updates animations and styling.
Layout/media/misc: card, table, avatar, aspect-ratio, separator, alert, toggle(+group)
packages/ui/src/components/ui/{card,table,avatar,aspect-ratio,separator,alert,toggle,toggle-group}.tsx
Refactors components, updates APIs and defaults, and adjusts exports and styles.

Account/auth flows and app UI/config updates

Layer / File(s) Summary
Validation helper and auth form wiring
packages/ui/src/lib/validation-errors.ts, packages/features/auth/src/{lib/schema.ts,password-reset/password-reset-form.tsx,sign-in/sign-in-email-password.tsx,sign-up/sign-up-email-password.tsx}
Adds onValidationErrors, customizes schema messages, and wires invalid handlers to show toasts.
Account hooks and avatar storage flow
packages/features/account/src/lib/hooks/{use-update-avatar.ts,use-fetch-profile.ts,use-update-account-mutation.ts}
Adds avatar update hook with storage ops and cache updates; centralizes query keys and invalidation.
Account profile/email/password components
packages/features/account/src/components/{update-profile-picture-form.tsx,update-profile-container.tsx,update-email-form.tsx,update-password-form.tsx}
Moves avatar flow to Expo media + hook, improves validation and error handling, and binds inputs explicitly.
App screens, layout, toaster, and settings UI
apps/expo-app/app/(app)/**, apps/expo-app/components/**
Switches to ButtonLink in auth screens, updates tab icons, adds themed Toaster, and uses Icon in settings and theme toggle.
Expo app config, Babel, and package manifests
apps/expo-app/{app.json,babel.config.js,package.json}, packages/features/account/package.json
Updates app config (android package, plugins), enables import.meta, and adjusts scripts/dependencies.

Supabase types/client and workspace/tooling updates

Layer / File(s) Summary
Supabase type helper rewrites (apps and package)
apps/expo-app/database.types.ts, packages/supabase/src/database.types.ts
Rebases helper types on DatabaseWithoutInternals/DefaultSchema and adds Constants; minor arg reordering.
Supabase client localhost handling and seed
packages/supabase/src/client.ts, apps/expo-app/supabase/seed.sql
Maps localhost to 10.0.2.2 on Android and adds a seed creating a confirmed test user/identity.
Workspace, package manager, and lint/ts config
package.json, pnpm-workspace.yaml, tooling/eslint-config/*, tooling/typescript-config/*, packages/supabase/package.json
Updates packageManager/scripts/pnpm built deps, refreshes catalog, disables no-undef, and adds node types.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Poem

A rabbit taps in Tailwind’s glow,
Prims and slots to functions go;
Icons twinkle, toasts align,
Avatars hop to clouds in time.
Tabs and menus dance in spree—
Supabase burrows locally.
Ship it quick—carrots on me! 🥕✨

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/features/auth/src/password-reset/password-reset-form.tsx (1)

43-50: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Wait for mutation success before showing success toast.

Line 49 shows toast.success(...) executing immediately after mutate(...) rather than waiting for the request to complete. If the password reset request fails, users will still see the success message because it displays before the mutation result is known.

Move the success toast into an onSuccess callback passed to the mutate() call. The hook itself (useRequestResetPassword) defines no success/error handlers, so notification logic belongs in the component calling the mutation.

Suggested fix
-          onPress={form.handleSubmit((data) => {
-            passwordResetMutation.mutate({
-              email: data.email,
-              redirectTo: '/',
-            });
-
-            toast.success('We sent you an email to reset your password.');
-          }, onValidationErrors((message) => toast.error(message)))}
+          onPress={form.handleSubmit((data) => {
+            passwordResetMutation.mutate(
+              {
+                email: data.email,
+                redirectTo: '/',
+              },
+              {
+                onSuccess: () => {
+                  toast.success('We sent you an email to reset your password.');
+                },
+                onError: () => {
+                  toast.error('Failed to send reset email. Please try again.');
+                },
+              },
+            );
+          }, onValidationErrors((message) => toast.error(message)))}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/features/auth/src/password-reset/password-reset-form.tsx` around
lines 43 - 50, The success toast is shown immediately after calling
passwordResetMutation.mutate instead of waiting for the request to complete;
update the form.handleSubmit handler so that mutate is called with an options
object containing an onSuccess callback that calls toast.success('We sent you an
email to reset your password.'), and keep validation errors routed through
onValidationErrors as before; reference the passwordResetMutation.mutate call in
the password-reset-form component (which uses useRequestResetPassword), and
optionally add an onError callback to the mutate options to show toast.error on
failure.
🧹 Nitpick comments (8)
packages/ui/src/components/ui/alert-dialog.tsx (1)

37-41: 💤 Low value

Redundant fragment wrapper around children.

The <>{children}</> wrapper is unnecessary since children can be passed directly to NativeOnlyAnimatedView.

♻️ Suggested simplification
         <NativeOnlyAnimatedView
           entering={FadeIn.duration(200).delay(50)}
           exiting={FadeOut.duration(150)}>
-          <>{children}</>
+          {children}
         </NativeOnlyAnimatedView>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/components/ui/alert-dialog.tsx` around lines 37 - 41, The JSX
wraps children in an unnecessary fragment inside NativeOnlyAnimatedView; update
the render in the alert dialog component to pass children directly to
NativeOnlyAnimatedView (remove the <> {children} </> wrapper) while keeping the
existing entering and exiting props (FadeIn.duration(200).delay(50) and
FadeOut.duration(150)) so only NativeOnlyAnimatedView, entering, exiting and
children remain.
packages/ui/src/components/ui/menubar.tsx (1)

55-62: ⚡ Quick win

Controlled/uncontrolled state precedence may be inverted.

The expression value={value ?? valueProp} gives precedence to internal state over the prop. For controlled components, the prop should typically take precedence. If a consumer passes value prop, they expect it to control the component.

Additionally, if onValueChangeProp is provided (controlled mode), the internal setValue is bypassed via closeMenu, but the onValueChange handler still falls back to setValue when onValueChangeProp is undefined, which is correct. However, the initial render and state reading logic could be clearer.

♻️ Suggested fix
       <MenubarPrimitive.Root
         className={cn(
           'bg-background border-border flex h-10 flex-row items-center gap-1 rounded-md border p-1 shadow-sm shadow-black/5 sm:h-9',
           className
         )}
-        value={value ?? valueProp}
+        value={valueProp ?? value}
         onValueChange={onValueChangeProp ?? setValue}
         {...props}
       />

This ensures that when the consumer provides a value prop (controlled mode), it takes precedence over internal state.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/components/ui/menubar.tsx` around lines 55 - 62, The Menubar
is giving internal state precedence over the controlled prop; change the
component to prioritize the consumer's prop by using valueProp before internal
value (i.e., pass value={valueProp ?? value} to MenubarPrimitive.Root), keep the
existing onValueChange handler pattern (onValueChange={onValueChangeProp ??
setValue}), and ensure the internal state initializer for value reads from
valueProp when present (initialize value state from valueProp as the default) so
controlled mode consistently wins and initial render is correct; reference
MenubarPrimitive.Root, valueProp, value, onValueChangeProp, setValue, and
closeMenu when making the edits.
packages/ui/src/components/ui/dialog.tsx (1)

40-44: 💤 Low value

Consider simplifying nested animation views.

Two NativeOnlyAnimatedView components are nested with slightly different timing. This adds unnecessary view hierarchy. The inner view only adds a 50ms delay, which could be combined into a single animation view.

♻️ Suggested simplification
       <DialogPrimitive.Overlay
         className={cn(...)}
         {...props}
         asChild={Platform.OS !== 'web'}>
-        <NativeOnlyAnimatedView entering={FadeIn.duration(200)} exiting={FadeOut.duration(150)}>
-          <NativeOnlyAnimatedView entering={FadeIn.delay(50)} exiting={FadeOut.duration(150)}>
-            <>{children}</>
-          </NativeOnlyAnimatedView>
-        </NativeOnlyAnimatedView>
+        <NativeOnlyAnimatedView
+          entering={FadeIn.duration(200).delay(50)}
+          exiting={FadeOut.duration(150)}>
+          {children}
+        </NativeOnlyAnimatedView>
       </DialogPrimitive.Overlay>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/components/ui/dialog.tsx` around lines 40 - 44, Replace the
nested NativeOnlyAnimatedView pair with a single NativeOnlyAnimatedView that
merges the timing (use FadeIn with the 50ms delay and 200ms duration and a
single FadeOut.duration(150) for exiting) and render {children} directly; update
the component that uses NativeOnlyAnimatedView (and references to
FadeIn/FadeOut) to remove the extra wrapper and redundant fragment so the
animation timings are combined in one view.
packages/ui/src/components/ui/tooltip.tsx (1)

29-35: 💤 Low value

Native animation only handles top/bottom cases.

The entering animation logic only differentiates between side === 'top' and other sides, while the web animation handles all four sides (bottom, left, right, top). If left/right tooltips are used on native, they'll get the FadeInUp animation which may not be the ideal direction.

If horizontal tooltips are not expected on mobile, this is fine as-is.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/components/ui/tooltip.tsx` around lines 29 - 35, The entering
animation only distinguishes side === 'top' vs other, causing left/right
tooltips to use FadeInUp; update the NativeOnlyAnimatedView entering logic (the
entering prop where side is checked) to handle 'left' and 'right' explicitly
(e.g., map 'left' -> FadeInLeft, 'right' -> FadeInRight, 'bottom' -> FadeInUp,
'top' -> FadeInDown) so mobile matches the web behavior; modify the
conditional/branch that currently uses FadeInDown and FadeInUp to a small switch
or lookup that returns the correct FadeIn* animation for each side.
packages/ui/src/components/ui/dropdown-menu.tsx (1)

106-114: 💤 Low value

Inaccurate type assertion for overlay style.

The type assertion overlayStyle as typeof StyleSheet.absoluteFill is not semantically correct. StyleSheet.absoluteFill returns a registered style ID (number), not a ViewStyle object. While StyleSheet.flatten handles both correctly at runtime, the type assertion is misleading.

♻️ Suggested fix
           native: overlayStyle
             ? StyleSheet.flatten([
                 StyleSheet.absoluteFill,
-                overlayStyle as typeof StyleSheet.absoluteFill,
+                overlayStyle,
               ])
             : StyleSheet.absoluteFill,

The StyleProp<ViewStyle> type for overlayStyle is already compatible with StyleSheet.flatten.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/components/ui/dropdown-menu.tsx` around lines 106 - 114, The
type assertion overlayStyle as typeof StyleSheet.absoluteFill is incorrect;
remove that assertion and pass overlayStyle directly to StyleSheet.flatten
(which accepts StyleProp<ViewStyle>), so modify the Platform.select branch used
in the component where overlayStyle is passed (the StyleSheet.flatten call
alongside StyleSheet.absoluteFill) to call
StyleSheet.flatten([StyleSheet.absoluteFill, overlayStyle]) without the cast,
ensuring overlayStyle retains its declared StyleProp<ViewStyle> type.
packages/ui/src/components/ui/context-menu.tsx (1)

100-108: 💤 Low value

Same type assertion issue as dropdown-menu.

Same as in dropdown-menu.tsx: the type assertion overlayStyle as typeof StyleSheet.absoluteFill is not semantically accurate.

♻️ Suggested fix
           native: overlayStyle
             ? StyleSheet.flatten([
                 StyleSheet.absoluteFill,
-                overlayStyle as typeof StyleSheet.absoluteFill,
+                overlayStyle,
               ])
             : StyleSheet.absoluteFill,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/components/ui/context-menu.tsx` around lines 100 - 108, The
code incorrectly asserts overlayStyle as typeof StyleSheet.absoluteFill; remove
that inaccurate assertion and ensure overlayStyle is typed as a ViewStyle (or
RegisteredStyle<ViewStyle>) instead. Update the ContextMenu/overlay block that
uses Platform.select, StyleSheet.flatten and StyleSheet.absoluteFill so the
flattened array is typed like [StyleSheet.absoluteFill, overlayStyle as
ViewStyle] (or cast overlayStyle where it is defined to ViewStyle) rather than
overlayStyle as typeof StyleSheet.absoluteFill; alternatively import ViewStyle
from react-native and declare overlayStyle: ViewStyle | undefined to satisfy the
type system.
packages/ui/src/components/ui/card.tsx (1)

24-50: ⚡ Quick win

Incorrect RefAttributes type and inconsistent ref forwarding.

  1. React.RefAttributes<typeof Text> should use the element type, not typeof Text. In React 19, React.ComponentProps already includes ref, making the separate RefAttributes redundant.

  2. CardTitle explicitly destructures and forwards ref (line 26, 32), but CardDescription does not, leading to inconsistent behavior despite identical type signatures.

Proposed fix
 function CardTitle({
   className,
-  ref,
   ...props
-}: React.ComponentProps<typeof Text> & React.RefAttributes<typeof Text>) {
-
+}: React.ComponentProps<typeof Text>) {
   return (
     <Text
-      ref={ref}
       role="heading"
       aria-level={3}
       // Kit extension: text-card-foreground text-2xl tracking-tight
       className={cn(
         'text-card-foreground text-2xl font-semibold leading-none tracking-tight',
         className,
       )}
       {...props}
     />
   );
 }

 function CardDescription({
   className,
   ...props
-}: React.ComponentProps<typeof Text> & React.RefAttributes<typeof Text>) {
+}: React.ComponentProps<typeof Text>) {
   return <Text className={cn('text-muted-foreground text-sm', className)} {...props} />;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/components/ui/card.tsx` around lines 24 - 50, The
CardTitle/CardDescription prop types use incorrect React.RefAttributes<typeof
Text> and ref handling is inconsistent: remove the explicit RefAttributes usage
and rely on React.ComponentProps<typeof Text> (which already includes ref in
React 18+/19), and make both components forward refs consistently by converting
them to React.forwardRef wrappers (forward the ref into the underlying Text
element in both CardTitle and CardDescription); update function signatures to
use React.forwardRef and ensure you forward the ref to the Text component
(preserve className merging with cn and other props) so both components have
correct types and consistent ref behavior.
packages/ui/src/components/ui/button.tsx (1)

95-97: ⚡ Quick win

Incorrect RefAttributes type parameter.

React.RefAttributes<typeof Pressable> passes the component constructor type rather than the element/instance type. In React 19, React.ComponentProps already includes the ref prop, so the additional RefAttributes is redundant and incorrectly typed.

Proposed fix
-type ButtonProps = React.ComponentProps<typeof Pressable> &
-  React.RefAttributes<typeof Pressable> &
-  VariantProps<typeof buttonVariants>;
+type ButtonProps = React.ComponentProps<typeof Pressable> &
+  VariantProps<typeof buttonVariants>;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/components/ui/button.tsx` around lines 95 - 97, The
ButtonProps union incorrectly includes React.RefAttributes<typeof Pressable>,
which uses the component constructor type and is redundant because
React.ComponentProps<typeof Pressable> already includes the ref in React 19;
remove the React.RefAttributes<typeof Pressable> part so ButtonProps becomes
React.ComponentProps<typeof Pressable> & VariantProps<typeof buttonVariants>, or
if an explicit ref type is required use
React.RefAttributes<React.ComponentRef<typeof Pressable>> instead.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/expo-app/app/`(app)/auth/sign-up.tsx:
- Line 33: Update the CTA string in the Text element inside the sign-up
component so it matches the surrounding sentence-case copy style; replace
"Already have an Account? Sign In." with "Already have an account? Sign in." in
the JSX Text node (the line rendering the CTA in
apps/expo-app/app/(app)/auth/sign-up.tsx).

In `@apps/expo-app/components/settings/theme-mode-toggle.tsx`:
- Around line 20-24: The onValueChange handler for the theme toggle currently
awaits AsyncStorage.setItem without catching rejections; wrap the await call in
a try/catch inside the onValueChange async function (the handler that calls
setColorScheme and AsyncStorage.setItem) to handle storage failures, log a
non-sensitive error via your logger or console.error, and consider reverting or
ensuring UI/state consistency if setting storage fails (e.g., keep
setColorScheme but surface the error); do not swallow the error silently and
include identifiable context like the theme value and that the failure occurred
in AsyncStorage.setItem.

In `@packages/features/account/src/lib/hooks/use-update-avatar.ts`:
- Around line 38-40: The current flow deletes the previous avatar via
deleteAvatarObject before the new upload/update is committed, and
storage.remove() errors are silently ignored; change the flow in
use-update-avatar (the hook) to first upload the new avatar and commit the
user/profile update referencing the new URL, then only after a successful commit
call deleteAvatarObject for the old URL so the user never loses the current
avatar if upload/update fails, and modify deleteAvatarObject (and any direct
storage.remove() call) to catch and handle errors (log and surface/return them
instead of swallowing) so storage.remove failures are handled comprehensively.
- Line 100: The remove(...) call is currently ignoring storage errors; change it
to follow the same pattern as uploadAvatarBytes by awaiting
client.storage.from(AVATARS_BUCKET).remove([fileName]) and destructuring the
result into { data, error } (or similar), then throw or propagate the error if
present so deletions aren't silently swallowed; update the remove call inside
useUpdateAvatar (referencing AVATARS_BUCKET and the client.storage.remove
invocation) to handle and surface any storage errors.

In `@packages/ui/src/components/ui/accordion.tsx`:
- Around line 1-14: The file uses React types via React.ComponentProps (seen in
the props for components such as the AccordionTrigger, AccordionItem,
AccordionContent, and others where React.ComponentProps is referenced) but never
imports React; add an import for React (e.g., import * as React from 'react' or
import React from 'react') at the top of the file so the type references
resolve, keeping the existing imports intact and without changing runtime
behavior.

In `@packages/ui/src/components/ui/alert.tsx`:
- Around line 1-11: The type aliases AlertTitleProps, AlertDescriptionProps,
AlertIconProps and AlertActionsProps use React.ComponentProps but React isn't
imported as a namespace; modify the top import from 'react' to also import the
type ComponentProps (e.g., add "type ComponentProps" to the import list) and
change those aliases to use ComponentProps<...> instead of
React.ComponentProps<...> so the types resolve correctly.

In `@packages/ui/src/components/ui/badge.tsx`:
- Line 43: The destructive badge variant uses a fixed Tailwind class
'text-white' which breaks theme semantics; in the badge variant map (look for
the object or constant assigning destructive: 'text-white' inside the Badge
component or its variants in badge.tsx) replace that fixed class with the
semantic token 'text-destructive-foreground' (or the project’s equivalent
semantic foreground token) so the destructive badge uses theme-aware foreground
styling instead of a hardcoded white.

In `@packages/ui/src/components/ui/native-only-animated-view.tsx`:
- Around line 16-19: The web branch currently returns a Fragment and drops
wrapper props; change the Platform.OS === 'web' branch to render a View that
spreads the incoming props and wraps children so className, style, pointerEvents
and event handlers are preserved (i.e., replace the Fragment with a View
{...props}>{props.children}</View>), and ensure View is imported if not already;
locate the web branch check around Platform.OS === 'web' in
native-only-animated-view.tsx and compare it with the else branch that returns
Animated.View to keep prop parity.

In `@packages/ui/src/components/ui/progress.tsx`:
- Around line 12-24: The Progress component destructures value but never passes
it to ProgressPrimitive.Root, which breaks accessibility; update the Progress
function so ProgressPrimitive.Root receives the value prop (i.e., include value
in the props passed to ProgressPrimitive.Root) while keeping Indicator receiving
value as before; locate the Progress component and modify the JSX for
ProgressPrimitive.Root to forward the value prop (retain existing
className/props and indicatorClassName handling for Indicator).

In `@packages/ui/src/components/ui/switch.tsx`:
- Around line 16-29: The styling uses props.checked which only works for
controlled mode; update the conditional class logic on SwitchPrimitives.Thumb
(and the outer Switch container if present) to rely on the primitive's
data-state attribute selectors instead of props.checked—remove branches that
check props.checked and replace with data-state="checked"/data-state="unchecked"
based class toggles so the visual translate-x and dark:bg classes reflect the
actual DOM state for both controlled and uncontrolled (defaultChecked) usage.

In `@packages/ui/src/components/ui/tabs.tsx`:
- Around line 1-4: The file uses the type helper React.ComponentProps in several
places (e.g., in the TabsPrimitive-related prop types and exported components)
but never imports React for type references; add an explicit import for React at
the top of the file (e.g., import * as React from 'react') so all
React.ComponentProps type usages (in the
TabsRoot/TabsList/TabsTrigger/TabsContent prop types and any other locations)
resolve correctly without changing runtime behavior.

In `@packages/ui/src/components/ui/text.tsx`:
- Around line 55-56: The block mapping in the text variant assigns invalid ARIA
roles by casting 'blockquote' and 'code' to Role; update the mapping in the Text
component (the object containing blockquote and code entries) to stop assigning
ARIA roles for these variants — remove the Platform.select(... as Role) casts
and either omit the role entries or set them to undefined so the semantic HTML
elements render without an explicit role; ensure no other code in the Text
component (or related render/variant handlers) relies on those role values.

In `@packages/ui/src/components/ui/toggle.tsx`:
- Around line 50-57: The TextClassContext value currently includes the full root
className via the cn(...) call which leaks layout/container classes; update the
value passed to TextClassContext in the toggle component so it only contains
text-related classes (e.g., the 'text-sm native:text-lg text-foreground
font-medium' base classes plus the pressed-dependent color from
props.pressed/Platform.select) and remove the root props.className from that
cn(...) invocation; keep props.className applied to the root element only and
ensure references to value={...}, cn(...), props.pressed, and
Platform.select(...) are adjusted accordingly.

In `@packages/ui/src/lib/validation-errors.ts`:
- Around line 7-11: The current top-level-only extraction (variables first and
firstMessage) misses nested FieldErrors; replace it with a small recursive
extractor (e.g., findFirstErrorMessage(errors)) that walks objects/arrays in the
supplied errors structure, returns the first encountered .message string
(coercing to String) and use that value instead of firstMessage; update the
places that reference first/firstMessage to call this helper so nested
FieldErrorsImpl entries are discovered.

In `@tooling/typescript-config/base.json`:
- Line 20: Remove the global "node" typing from the base compilerOptions.types
in tooling/typescript-config/base.json so the base no longer forces Node types
on all packages; update the "types" array (currently ["nativewind/types",
"node"]) to omit "node", and add a note or document that packages which require
Node typings (e.g., the supabase package that uses process.env) should
explicitly opt-in by adding "node" to their own compilerOptions.types in their
local tsconfig (packages/ui, packages/features/auth, packages/features/account,
packages/supabase as needed).

---

Outside diff comments:
In `@packages/features/auth/src/password-reset/password-reset-form.tsx`:
- Around line 43-50: The success toast is shown immediately after calling
passwordResetMutation.mutate instead of waiting for the request to complete;
update the form.handleSubmit handler so that mutate is called with an options
object containing an onSuccess callback that calls toast.success('We sent you an
email to reset your password.'), and keep validation errors routed through
onValidationErrors as before; reference the passwordResetMutation.mutate call in
the password-reset-form component (which uses useRequestResetPassword), and
optionally add an onError callback to the mutate options to show toast.error on
failure.

---

Nitpick comments:
In `@packages/ui/src/components/ui/alert-dialog.tsx`:
- Around line 37-41: The JSX wraps children in an unnecessary fragment inside
NativeOnlyAnimatedView; update the render in the alert dialog component to pass
children directly to NativeOnlyAnimatedView (remove the <> {children} </>
wrapper) while keeping the existing entering and exiting props
(FadeIn.duration(200).delay(50) and FadeOut.duration(150)) so only
NativeOnlyAnimatedView, entering, exiting and children remain.

In `@packages/ui/src/components/ui/button.tsx`:
- Around line 95-97: The ButtonProps union incorrectly includes
React.RefAttributes<typeof Pressable>, which uses the component constructor type
and is redundant because React.ComponentProps<typeof Pressable> already includes
the ref in React 19; remove the React.RefAttributes<typeof Pressable> part so
ButtonProps becomes React.ComponentProps<typeof Pressable> & VariantProps<typeof
buttonVariants>, or if an explicit ref type is required use
React.RefAttributes<React.ComponentRef<typeof Pressable>> instead.

In `@packages/ui/src/components/ui/card.tsx`:
- Around line 24-50: The CardTitle/CardDescription prop types use incorrect
React.RefAttributes<typeof Text> and ref handling is inconsistent: remove the
explicit RefAttributes usage and rely on React.ComponentProps<typeof Text>
(which already includes ref in React 18+/19), and make both components forward
refs consistently by converting them to React.forwardRef wrappers (forward the
ref into the underlying Text element in both CardTitle and CardDescription);
update function signatures to use React.forwardRef and ensure you forward the
ref to the Text component (preserve className merging with cn and other props)
so both components have correct types and consistent ref behavior.

In `@packages/ui/src/components/ui/context-menu.tsx`:
- Around line 100-108: The code incorrectly asserts overlayStyle as typeof
StyleSheet.absoluteFill; remove that inaccurate assertion and ensure
overlayStyle is typed as a ViewStyle (or RegisteredStyle<ViewStyle>) instead.
Update the ContextMenu/overlay block that uses Platform.select,
StyleSheet.flatten and StyleSheet.absoluteFill so the flattened array is typed
like [StyleSheet.absoluteFill, overlayStyle as ViewStyle] (or cast overlayStyle
where it is defined to ViewStyle) rather than overlayStyle as typeof
StyleSheet.absoluteFill; alternatively import ViewStyle from react-native and
declare overlayStyle: ViewStyle | undefined to satisfy the type system.

In `@packages/ui/src/components/ui/dialog.tsx`:
- Around line 40-44: Replace the nested NativeOnlyAnimatedView pair with a
single NativeOnlyAnimatedView that merges the timing (use FadeIn with the 50ms
delay and 200ms duration and a single FadeOut.duration(150) for exiting) and
render {children} directly; update the component that uses
NativeOnlyAnimatedView (and references to FadeIn/FadeOut) to remove the extra
wrapper and redundant fragment so the animation timings are combined in one
view.

In `@packages/ui/src/components/ui/dropdown-menu.tsx`:
- Around line 106-114: The type assertion overlayStyle as typeof
StyleSheet.absoluteFill is incorrect; remove that assertion and pass
overlayStyle directly to StyleSheet.flatten (which accepts
StyleProp<ViewStyle>), so modify the Platform.select branch used in the
component where overlayStyle is passed (the StyleSheet.flatten call alongside
StyleSheet.absoluteFill) to call StyleSheet.flatten([StyleSheet.absoluteFill,
overlayStyle]) without the cast, ensuring overlayStyle retains its declared
StyleProp<ViewStyle> type.

In `@packages/ui/src/components/ui/menubar.tsx`:
- Around line 55-62: The Menubar is giving internal state precedence over the
controlled prop; change the component to prioritize the consumer's prop by using
valueProp before internal value (i.e., pass value={valueProp ?? value} to
MenubarPrimitive.Root), keep the existing onValueChange handler pattern
(onValueChange={onValueChangeProp ?? setValue}), and ensure the internal state
initializer for value reads from valueProp when present (initialize value state
from valueProp as the default) so controlled mode consistently wins and initial
render is correct; reference MenubarPrimitive.Root, valueProp, value,
onValueChangeProp, setValue, and closeMenu when making the edits.

In `@packages/ui/src/components/ui/tooltip.tsx`:
- Around line 29-35: The entering animation only distinguishes side === 'top' vs
other, causing left/right tooltips to use FadeInUp; update the
NativeOnlyAnimatedView entering logic (the entering prop where side is checked)
to handle 'left' and 'right' explicitly (e.g., map 'left' -> FadeInLeft, 'right'
-> FadeInRight, 'bottom' -> FadeInUp, 'top' -> FadeInDown) so mobile matches the
web behavior; modify the conditional/branch that currently uses FadeInDown and
FadeInUp to a small switch or lookup that returns the correct FadeIn* animation
for each side.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e28e008e-2161-42db-9c0f-c06cc2653f09

📥 Commits

Reviewing files that changed from the base of the PR and between 6e225af and ff6ec7e.

⛔ Files ignored due to path filters (2)
  • apps/expo-app/package-lock.json is excluded by !**/package-lock.json
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (72)
  • apps/expo-app/app.json
  • apps/expo-app/app/(app)/(main)/_layout.tsx
  • apps/expo-app/app/(app)/auth/error.tsx
  • apps/expo-app/app/(app)/auth/password-reset.tsx
  • apps/expo-app/app/(app)/auth/sign-in.tsx
  • apps/expo-app/app/(app)/auth/sign-up.tsx
  • apps/expo-app/babel.config.js
  • apps/expo-app/components/root-provider.tsx
  • apps/expo-app/components/settings/settings-pages-list.tsx
  • apps/expo-app/components/settings/theme-mode-toggle.tsx
  • apps/expo-app/database.types.ts
  • apps/expo-app/package.json
  • apps/expo-app/supabase/seed.sql
  • package.json
  • packages/features/account/package.json
  • packages/features/account/src/components/update-email-form.tsx
  • packages/features/account/src/components/update-password-form.tsx
  • packages/features/account/src/components/update-profile-container.tsx
  • packages/features/account/src/components/update-profile-picture-form.tsx
  • packages/features/account/src/lib/hooks/use-fetch-profile.ts
  • packages/features/account/src/lib/hooks/use-update-account-mutation.ts
  • packages/features/account/src/lib/hooks/use-update-avatar.ts
  • packages/features/account/src/lib/hooks/use-update-password.ts
  • packages/features/auth/src/lib/schema.ts
  • packages/features/auth/src/password-reset/password-reset-form.tsx
  • packages/features/auth/src/sign-in/sign-in-email-password.tsx
  • packages/features/auth/src/sign-up/sign-up-email-password.tsx
  • packages/supabase/package.json
  • packages/supabase/src/client.ts
  • packages/supabase/src/database.types.ts
  • packages/ui/components.json
  • packages/ui/package.json
  • packages/ui/src/components/ui/accordion.tsx
  • packages/ui/src/components/ui/alert-dialog.tsx
  • packages/ui/src/components/ui/alert.tsx
  • packages/ui/src/components/ui/aspect-ratio.tsx
  • packages/ui/src/components/ui/avatar.tsx
  • packages/ui/src/components/ui/badge.tsx
  • packages/ui/src/components/ui/button.tsx
  • packages/ui/src/components/ui/card.tsx
  • packages/ui/src/components/ui/checkbox.tsx
  • packages/ui/src/components/ui/context-menu.tsx
  • packages/ui/src/components/ui/dialog.tsx
  • packages/ui/src/components/ui/dropdown-menu.tsx
  • packages/ui/src/components/ui/hover-card.tsx
  • packages/ui/src/components/ui/icon.tsx
  • packages/ui/src/components/ui/input.tsx
  • packages/ui/src/components/ui/label.tsx
  • packages/ui/src/components/ui/menubar.tsx
  • packages/ui/src/components/ui/native-only-animated-view.tsx
  • packages/ui/src/components/ui/navigation-menu.tsx
  • packages/ui/src/components/ui/popover.tsx
  • packages/ui/src/components/ui/progress.tsx
  • packages/ui/src/components/ui/radio-group.tsx
  • packages/ui/src/components/ui/select.tsx
  • packages/ui/src/components/ui/separator.tsx
  • packages/ui/src/components/ui/switch.tsx
  • packages/ui/src/components/ui/table.tsx
  • packages/ui/src/components/ui/tabs.tsx
  • packages/ui/src/components/ui/text.tsx
  • packages/ui/src/components/ui/textarea.tsx
  • packages/ui/src/components/ui/toggle-group.tsx
  • packages/ui/src/components/ui/toggle.tsx
  • packages/ui/src/components/ui/tooltip.tsx
  • packages/ui/src/components/ui/typography.tsx
  • packages/ui/src/index.ts
  • packages/ui/src/lib/validation-errors.ts
  • pnpm-workspace.yaml
  • tooling/eslint-config/base.js
  • tooling/eslint-config/package.json
  • tooling/typescript-config/base.json
  • tooling/typescript-config/package.json
💤 Files with no reviewable changes (2)
  • packages/ui/src/components/ui/typography.tsx
  • tooling/eslint-config/package.json

</Link>
</Button>
<ButtonLink variant="link" href="/auth/sign-in">
<Text className="text-center">Already have an Account? Sign In.</Text>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix CTA capitalization for consistency.

Line 33 uses inconsistent title casing (Account, Sign In) compared with surrounding copy style.

Suggested copy tweak
-          <Text className="text-center">Already have an Account? Sign In.</Text>
+          <Text className="text-center">Already have an account? Sign in.</Text>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Text className="text-center">Already have an Account? Sign In.</Text>
<Text className="text-center">Already have an account? Sign in.</Text>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/expo-app/app/`(app)/auth/sign-up.tsx at line 33, Update the CTA string
in the Text element inside the sign-up component so it matches the surrounding
sentence-case copy style; replace "Already have an Account? Sign In." with
"Already have an account? Sign in." in the JSX Text node (the line rendering the
CTA in apps/expo-app/app/(app)/auth/sign-up.tsx).

Comment on lines 20 to 24
onValueChange={async (value) => {
if (!value) return;
setColorScheme(value as Theme);
await AsyncStorage.setItem('theme', value as Theme);
await AsyncStorage.setItem('theme', value);
}}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle AsyncStorage failures in the toggle handler.

Line 23 can reject and currently has no local handling, which risks unhandled promise rejections and silent preference persistence failure.

Suggested fix
         onValueChange={async (value) => {
           if (!value) return;
-          setColorScheme(value as Theme);
-          await AsyncStorage.setItem('theme', value);
+          setColorScheme(value as Theme);
+          try {
+            await AsyncStorage.setItem('theme', value);
+          } catch (error) {
+            console.error('Failed to persist theme preference', error);
+          }
         }}

As per coding guidelines, "Do not swallow errors; handle promises and async/await with proper error handling and non-sensitive context".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
onValueChange={async (value) => {
if (!value) return;
setColorScheme(value as Theme);
await AsyncStorage.setItem('theme', value as Theme);
await AsyncStorage.setItem('theme', value);
}}
onValueChange={async (value) => {
if (!value) return;
setColorScheme(value as Theme);
try {
await AsyncStorage.setItem('theme', value);
} catch (error) {
console.error('Failed to persist theme preference', error);
}
}}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/expo-app/components/settings/theme-mode-toggle.tsx` around lines 20 -
24, The onValueChange handler for the theme toggle currently awaits
AsyncStorage.setItem without catching rejections; wrap the await call in a
try/catch inside the onValueChange async function (the handler that calls
setColorScheme and AsyncStorage.setItem) to handle storage failures, log a
non-sensitive error via your logger or console.error, and consider reverting or
ensuring UI/state consistency if setting storage fails (e.g., keep
setColorScheme but surface the error); do not swallow the error silently and
include identifiable context like the theme value and that the failure occurred
in AsyncStorage.setItem.

Comment on lines +38 to +40
if (previousPictureUrl) {
await deleteAvatarObject(client, previousPictureUrl);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n -C6 'previousPictureUrl|deleteAvatarObject|uploadAvatarBytes|update\(\{ picture_url' packages/features/account/src/lib/hooks/use-update-avatar.ts

Repository: makerkit/react-native-expo-turbo-saas-kit

Length of output: 2165


🏁 Script executed:

#!/bin/bash
# Get the complete file to see the full deleteAvatarObject function
cat -n packages/features/account/src/lib/hooks/use-update-avatar.ts | tail -20

Repository: makerkit/react-native-expo-turbo-saas-kit

Length of output: 732


Don't delete the current avatar before the replacement is committed.

Line 38 deletes the existing object before upload/update. If line 51 or lines 55-56 fail, the user loses their current avatar. Additionally, line 100 silently ignores errors from the storage remove() call; per the guidelines, errors must be handled comprehensively.

Proposed fix
-      if (previousPictureUrl) {
-        await deleteAvatarObject(client, previousPictureUrl);
-      }
-
       if (uri === null) {
+        if (previousPictureUrl) {
+          await deleteAvatarObject(client, previousPictureUrl);
+        }
         await client
           .from('accounts')
           .update({ picture_url: null })
           .eq('id', userId)
           .throwOnError();
         return null;
       }

       const publicUrl = await uploadAvatarBytes(client, uri, userId);

       await client
         .from('accounts')
         .update({ picture_url: publicUrl })
         .eq('id', userId)
         .throwOnError();
+
+      if (previousPictureUrl) {
+        await deleteAvatarObject(client, previousPictureUrl);
+      }

       return publicUrl;

Also update deleteAvatarObject to handle errors:

 async function deleteAvatarObject(
   client: SupabaseClient<Database>,
   url: string,
 ) {
   // Strip the cache-buster query string to recover the storage key.
   const fileName = url.split('/').pop()?.split('?')[0];
   if (!fileName) return;

-  await client.storage.from(AVATARS_BUCKET).remove([fileName]);
+  const { error } = await client.storage.from(AVATARS_BUCKET).remove([fileName]);
+  if (error) throw error;
 }

Also applies to: 51-59, 100

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/features/account/src/lib/hooks/use-update-avatar.ts` around lines 38
- 40, The current flow deletes the previous avatar via deleteAvatarObject before
the new upload/update is committed, and storage.remove() errors are silently
ignored; change the flow in use-update-avatar (the hook) to first upload the new
avatar and commit the user/profile update referencing the new URL, then only
after a successful commit call deleteAvatarObject for the old URL so the user
never loses the current avatar if upload/update fails, and modify
deleteAvatarObject (and any direct storage.remove() call) to catch and handle
errors (log and surface/return them instead of swallowing) so storage.remove
failures are handled comprehensively.

const fileName = url.split('/').pop()?.split('?')[0];
if (!fileName) return;

await client.storage.from(AVATARS_BUCKET).remove([fileName]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n -C3 'remove\(\[fileName\]\)' packages/features/account/src/lib/hooks/use-update-avatar.ts

Repository: makerkit/react-native-expo-turbo-saas-kit

Length of output: 250


🏁 Script executed:

cat -n packages/features/account/src/lib/hooks/use-update-avatar.ts

Repository: makerkit/react-native-expo-turbo-saas-kit

Length of output: 3828


Handle storage-delete errors instead of swallowing them.

Line 100 ignores remove(...) errors, which can silently leave old avatar files behind. This violates the error-handling requirement in the coding guidelines. The codebase already demonstrates the correct pattern in uploadAvatarBytes (lines 82–87), which properly destructures and throws storage errors.

Proposed fix
-  await client.storage.from(AVATARS_BUCKET).remove([fileName]);
+  const { error } = await client.storage.from(AVATARS_BUCKET).remove([fileName]);
+  if (error) throw error;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await client.storage.from(AVATARS_BUCKET).remove([fileName]);
const { error } = await client.storage.from(AVATARS_BUCKET).remove([fileName]);
if (error) throw error;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/features/account/src/lib/hooks/use-update-avatar.ts` at line 100,
The remove(...) call is currently ignoring storage errors; change it to follow
the same pattern as uploadAvatarBytes by awaiting
client.storage.from(AVATARS_BUCKET).remove([fileName]) and destructuring the
result into { data, error } (or similar), then throw or propagate the error if
present so deletions aren't silently swallowed; update the remove call inside
useUpdateAvatar (referencing AVATARS_BUCKET and the client.storage.remove
invocation) to handle and surface any storage errors.

Comment on lines +1 to 14
import { Icon } from './icon';
import { TextClassContext } from './text';
import { cn } from '../../lib/utils';
import * as AccordionPrimitive from '@rn-primitives/accordion';
import { ChevronDown } from 'lucide-react-native';
import { Platform, Pressable, View } from 'react-native';
import Animated, {
Extrapolation,
FadeIn,
FadeOutUp,
LayoutAnimationConfig,
LinearTransition,
interpolate,
useAnimatedStyle,
useDerivedValue,
withTiming,
} from 'react-native-reanimated';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Missing React import for type usage.

React.ComponentProps is used at lines 19, 36, 62, and 119, but React is not imported.

Proposed fix
+import * as React from 'react';
 import { Icon } from './icon';
 import { TextClassContext } from './text';
 import { cn } from '../../lib/utils';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { Icon } from './icon';
import { TextClassContext } from './text';
import { cn } from '../../lib/utils';
import * as AccordionPrimitive from '@rn-primitives/accordion';
import { ChevronDown } from 'lucide-react-native';
import { Platform, Pressable, View } from 'react-native';
import Animated, {
Extrapolation,
FadeIn,
FadeOutUp,
LayoutAnimationConfig,
LinearTransition,
interpolate,
useAnimatedStyle,
useDerivedValue,
withTiming,
} from 'react-native-reanimated';
import * as React from 'react';
import { Icon } from './icon';
import { TextClassContext } from './text';
import { cn } from '../../lib/utils';
import * as AccordionPrimitive from '`@rn-primitives/accordion`';
import { ChevronDown } from 'lucide-react-native';
import { Platform, Pressable, View } from 'react-native';
import Animated, {
FadeOutUp,
LayoutAnimationConfig,
LinearTransition,
useAnimatedStyle,
useDerivedValue,
withTiming,
} from 'react-native-reanimated';
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/components/ui/accordion.tsx` around lines 1 - 14, The file
uses React types via React.ComponentProps (seen in the props for components such
as the AccordionTrigger, AccordionItem, AccordionContent, and others where
React.ComponentProps is referenced) but never imports React; add an import for
React (e.g., import * as React from 'react' or import React from 'react') at the
top of the file so the type references resolve, keeping the existing imports
intact and without changing runtime behavior.

Comment on lines 1 to +4
import { TextClassContext } from './text';
import { cn } from '../../lib/utils';
import * as TabsPrimitive from '@rn-primitives/tabs';
import { Platform } from 'react-native';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Missing React import for type usage.

React.ComponentProps is used throughout the file (lines 9, 16, 32, 60), but React is not imported. While the JSX transform auto-injects React for JSX, the type reference React.ComponentProps requires an explicit import.

Proposed fix
+import * as React from 'react';
 import { TextClassContext } from './text';
 import { cn } from '../../lib/utils';
 import * as TabsPrimitive from '`@rn-primitives/tabs`';
 import { Platform } from 'react-native';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { TextClassContext } from './text';
import { cn } from '../../lib/utils';
import * as TabsPrimitive from '@rn-primitives/tabs';
import { Platform } from 'react-native';
import * as React from 'react';
import { TextClassContext } from './text';
import { cn } from '../../lib/utils';
import * as TabsPrimitive from '`@rn-primitives/tabs`';
import { Platform } from 'react-native';
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/components/ui/tabs.tsx` around lines 1 - 4, The file uses the
type helper React.ComponentProps in several places (e.g., in the
TabsPrimitive-related prop types and exported components) but never imports
React for type references; add an explicit import for React at the top of the
file (e.g., import * as React from 'react') so all React.ComponentProps type
usages (in the TabsRoot/TabsList/TabsTrigger/TabsContent prop types and any
other locations) resolve correctly without changing runtime behavior.

Comment on lines +55 to +56
blockquote: Platform.select({ web: 'blockquote' as Role }),
code: Platform.select({ web: 'code' as Role }),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

cd /tmp && find . -name "text.tsx" -path "*/packages/ui/*" 2>/dev/null | head -5

Repository: makerkit/react-native-expo-turbo-saas-kit

Length of output: 67


🏁 Script executed:

git ls-files packages/ui/src/components/ui/ | grep -E "\.(ts|tsx)$"

Repository: makerkit/react-native-expo-turbo-saas-kit

Length of output: 1688


🏁 Script executed:

cat -n packages/ui/src/components/ui/text.tsx | head -100

Repository: makerkit/react-native-expo-turbo-saas-kit

Length of output: 4142


🏁 Script executed:

rg "type Role|export.*Role" packages/ui/src/ --type ts --type tsx

Repository: makerkit/react-native-expo-turbo-saas-kit

Length of output: 113


🌐 Web query:

ARIA valid roles specification 2024 blockquote code roles

💡 Result:

In ARIA, the valid/appropriate role value for an HTML

is

Citations:


🏁 Script executed:

rg "blockquote|code" packages/ui/src/components/ui/text.tsx -A 2 -B 2

Repository: makerkit/react-native-expo-turbo-saas-kit

Length of output: 1049


🏁 Script executed:

rg "Platform.select" packages/ui/src/components/ui/text.tsx -A 1 -B 1

Repository: makerkit/react-native-expo-turbo-saas-kit

Length of output: 816


🏁 Script executed:

# Check if there are type stubs or node_modules references for react-native Role
find . -name "*.d.ts" -o -name "*.ts" | xargs grep -l "type Role" | head -10

Repository: makerkit/react-native-expo-turbo-saas-kit

Length of output: 67


🏁 Script executed:

# Look for how other components handle optional roles on mobile vs web
rg "Platform.select.*Role" packages/ui/src/ -A 1 -B 1

Repository: makerkit/react-native-expo-turbo-saas-kit

Length of output: 373


🌐 Web query:

ARIA valid roles list W3C specification 2024

💡 Result:

As of May 2026, the W3C WAI-ARIA 1.2 specification remains the current W3C Recommendation [1][2]. While WAI-ARIA 1.3 is under active development as a draft [3][1], it has not yet reached the status of a final W3C Recommendation. The authoritative list of valid ARIA roles is defined in the WAI-ARIA specification [4][5]. These roles are categorized into several types, including landmark, widget, document structure, and abstract roles [6][1]. Key points regarding ARIA roles: 1. Current Standard: WAI-ARIA 1.2 is the stable, recommended version [1][2]. 2. Development Status: WAI-ARIA 1.3 is in development [3][1]. It introduces proposed new roles such as suggestion, comment, and mark [3][1]. Because these are part of a draft, they should be treated as experimental until the specification is finalized [1]. 3. Abstract Roles: The specification defines abstract roles (e.g., widget, structure, input) which are used for the ontology of the specification itself [6][1]. Authors must not use abstract roles in their content [6]. 4. Usage: Authors should use the role attribute to assign these roles to HTML elements [6][7]. However, it is generally recommended to use native HTML elements whenever possible, as they often have implicit ARIA semantics that should not be overridden [8][9]. For the most accurate and up-to-date list of roles, you should consult the official W3C WAI-ARIA specification documents [4][5]. The MDN Web Docs also provide a comprehensive and developer-friendly reference for these roles [6].

Citations:


Remove invalid ARIA role assignments for blockquote and code variants.

blockquote and code are not valid ARIA role values per the W3C WAI-ARIA 1.2 specification. These are semantic HTML elements that should not have explicit role attributes. Casting them to Role on lines 55–56 bypasses TypeScript type safety and produces invalid accessibility markup.

Proposed fix
 const ROLE: Partial<Record<TextVariant, Role>> = {
   h1: 'heading',
   h2: 'heading',
   h3: 'heading',
   h4: 'heading',
-  blockquote: Platform.select({ web: 'blockquote' as Role }),
-  code: Platform.select({ web: 'code' as Role }),
 };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/components/ui/text.tsx` around lines 55 - 56, The block
mapping in the text variant assigns invalid ARIA roles by casting 'blockquote'
and 'code' to Role; update the mapping in the Text component (the object
containing blockquote and code entries) to stop assigning ARIA roles for these
variants — remove the Platform.select(... as Role) casts and either omit the
role entries or set them to undefined so the semantic HTML elements render
without an explicit role; ensure no other code in the Text component (or related
render/variant handlers) relies on those role values.

Comment on lines +50 to +57
value={cn(
// Kit extension: native:text-lg
'text-sm native:text-lg text-foreground font-medium',
props.pressed
? 'text-accent-foreground'
: Platform.select({ web: 'group-hover:text-muted-foreground' }),
className
)}>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Avoid passing root className into TextClassContext.

This leaks layout/container classes into nested text/icon styling. Keep context value text-only.

Suggested fix
     <TextClassContext.Provider
       value={cn(
         // Kit extension: native:text-lg
         'text-sm native:text-lg text-foreground font-medium',
         props.pressed
           ? 'text-accent-foreground'
-          : Platform.select({ web: 'group-hover:text-muted-foreground' }),
-        className
+          : Platform.select({ web: 'group-hover:text-muted-foreground' })
       )}>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
value={cn(
// Kit extension: native:text-lg
'text-sm native:text-lg text-foreground font-medium',
props.pressed
? 'text-accent-foreground'
: Platform.select({ web: 'group-hover:text-muted-foreground' }),
className
)}>
value={cn(
// Kit extension: native:text-lg
'text-sm native:text-lg text-foreground font-medium',
props.pressed
? 'text-accent-foreground'
: Platform.select({ web: 'group-hover:text-muted-foreground' })
)}>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/components/ui/toggle.tsx` around lines 50 - 57, The
TextClassContext value currently includes the full root className via the
cn(...) call which leaks layout/container classes; update the value passed to
TextClassContext in the toggle component so it only contains text-related
classes (e.g., the 'text-sm native:text-lg text-foreground font-medium' base
classes plus the pressed-dependent color from props.pressed/Platform.select) and
remove the root props.className from that cn(...) invocation; keep
props.className applied to the root element only and ensure references to
value={...}, cn(...), props.pressed, and Platform.select(...) are adjusted
accordingly.

Comment on lines +7 to +11
const first = Object.values(errors).find(
(e) => e && 'message' in e && e.message,
);
const firstMessage =
first && 'message' in first && first.message ? String(first.message) : '';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

cat -n packages/ui/src/lib/validation-errors.ts

Repository: makerkit/react-native-expo-turbo-saas-kit

Length of output: 624


🏁 Script executed:

rg "FieldErrors" --type ts --type tsx -A 3 -B 3

Repository: makerkit/react-native-expo-turbo-saas-kit

Length of output: 113


🏁 Script executed:

fd -e ts -e tsx | xargs rg "from 'react-hook-form'" | head -20

Repository: makerkit/react-native-expo-turbo-saas-kit

Length of output: 897


🏁 Script executed:

rg "onValidationErrors" -A 5 -B 2

Repository: makerkit/react-native-expo-turbo-saas-kit

Length of output: 9429


🏁 Script executed:

rg "FieldErrors" -A 2 -B 2 --type ts

Repository: makerkit/react-native-expo-turbo-saas-kit

Length of output: 699


🏁 Script executed:

web_search
React Hook Form FieldErrors nested structure

Repository: makerkit/react-native-expo-turbo-saas-kit

Length of output: 180


🌐 Web query:

React Hook Form FieldErrors nested structure type definition

💡 Result:

In React Hook Form, the FieldErrors type represents the structure of form validation errors, which mirrors the shape of your form data [1]. Because form data can be deeply nested, FieldErrors uses a recursive type definition to maintain this hierarchy [1]. The core type definition is FieldErrors, which maps your form values (T) to an error object [1]. For nested objects, it utilizes FieldErrorsImpl, which recursively traverses the object structure [1]. Key aspects of the FieldErrors structure include: 1. Recursive Mapping: For any property in your form data that is an object, FieldErrors will contain a corresponding nested object [1]. 2. FieldError Type: At the leaf nodes (the actual fields), the type is FieldError [1]. This type contains details about the validation failure, such as: - type: The validation rule that failed (e.g., 'required', 'min') [1][2]. - message: The error message string [1][2]. - ref: A reference to the input element [1][2]. - types: An object containing multiple validation errors if multiple rules failed for the same field [1][2]. 3. Handling Nested Objects: When a field is an object, the corresponding entry in the errors object is a combination of FieldError (if the object itself has an error) and the nested FieldErrorsImpl for its children [1]. The official type definition from the React Hook Form source code is as follows [1]: type FieldErrorsImpl = { [K in keyof T]?: T[K] extends BrowserNativeObject | Blob? FieldError: K extends 'root' | root.${string}? GlobalError: T[K] extends object? Merge<FieldError, FieldErrorsImpl<T[K]>>: FieldError; }; This structure ensures that you can access nested errors using standard dot notation (e.g., errors.user?.address?.city) [3][4]. If you encounter issues where the TypeScript type does not seem to match your nested structure, it is often due to the complexity of recursive types in TypeScript; developers sometimes use type casting or utility types like DeepPartial to work around specific edge cases [5][1].

Citations:


🏁 Script executed:

rg "useForm\|FormState\|FieldArray" packages/features -A 10 | head -100

Repository: makerkit/react-native-expo-turbo-saas-kit

Length of output: 67


🏁 Script executed:

# Check if any forms use nested field structures
rg "register.*\." packages/features -A 2 | head -50

Repository: makerkit/react-native-expo-turbo-saas-kit

Length of output: 67


Add recursive error extraction to handle nested field errors.

The current implementation at lines 7–11 only checks top-level error entries. React Hook Form's FieldErrors type is recursive and supports nested objects (via FieldErrorsImpl<T>), so deeply nested field errors will be missed, causing silent toast failures.

Proposed fix
+function findFirstMessage(value: unknown): string | undefined {
+  if (!value || typeof value !== 'object') return undefined;
+  if ('message' in value && value.message) return String(value.message);
+  for (const child of Object.values(value as Record<string, unknown>)) {
+    const found = findFirstMessage(child);
+    if (found) return found;
+  }
+  return undefined;
+}
+
 export function onValidationErrors(
   handler: (firstMessage: string, errors: FieldErrors) => void,
 ) {
   return (errors: FieldErrors) => {
-    const first = Object.values(errors).find(
-      (e) => e && 'message' in e && e.message,
-    );
-    const firstMessage =
-      first && 'message' in first && first.message ? String(first.message) : '';
+    const firstMessage = findFirstMessage(errors) ?? '';
     handler(firstMessage, errors);
   };
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/lib/validation-errors.ts` around lines 7 - 11, The current
top-level-only extraction (variables first and firstMessage) misses nested
FieldErrors; replace it with a small recursive extractor (e.g.,
findFirstErrorMessage(errors)) that walks objects/arrays in the supplied errors
structure, returns the first encountered .message string (coercing to String)
and use that value instead of firstMessage; update the places that reference
first/firstMessage to call this helper so nested FieldErrorsImpl entries are
discovered.

"incremental": true,
"noUncheckedIndexedAccess": true,
"types": ["nativewind/types"]
"types": ["nativewind/types", "node"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== tsconfig files extending `@kit/typescript-config/base` =="
rg -n --glob '**/tsconfig*.json' '"extends"\s*:\s*".*typescript-config.*/base(\.json)?"'

echo
echo "== tsconfig files explicitly adding compilerOptions.types =="
rg -n --glob '**/tsconfig*.json' '"types"\s*:'

echo
echo "== Potential Node-global usage in Expo app sources (sanity check) =="
rg -n --glob 'apps/expo-app/**' '\b(process|Buffer|global|__dirname|__filename)\b' || true

Repository: makerkit/react-native-expo-turbo-saas-kit

Length of output: 716


🏁 Script executed:

cat -n tooling/typescript-config/base.json

Repository: makerkit/react-native-expo-turbo-saas-kit

Length of output: 878


🏁 Script executed:

cat -n apps/expo-app/tsconfig.json

Repository: makerkit/react-native-expo-turbo-saas-kit

Length of output: 691


🏁 Script executed:

# Check if any of the inheriting packages are client/native targets
for file in packages/ui/tsconfig.json packages/features/auth/tsconfig.json packages/features/account/tsconfig.json packages/supabase/tsconfig.json; do
  echo "=== $file ==="
  rg -A 5 -B 2 '"compilerOptions"' "$file" | head -20
done

Repository: makerkit/react-native-expo-turbo-saas-kit

Length of output: 901


🏁 Script executed:

# Check for Node-specific API usage in the shared packages that inherit from base
for package_dir in packages/ui packages/features/auth packages/features/account packages/supabase; do
  echo "=== Checking $package_dir for Node APIs ==="
  rg -l '\b(process|Buffer|fs\.|path\.|require\(|__dirname|__filename)\b' "$package_dir/src" 2>/dev/null || echo "  No Node APIs found"
done

Repository: makerkit/react-native-expo-turbo-saas-kit

Length of output: 383


🏁 Script executed:

# Check package.json files to understand what these packages are
for package_dir in packages/ui packages/features/auth packages/features/account packages/supabase; do
  if [ -f "$package_dir/package.json" ]; then
    echo "=== $package_dir/package.json (key fields) ==="
    rg -A 3 '"(name|description|main|exports)"' "$package_dir/package.json" | head -15
  fi
done

Repository: makerkit/react-native-expo-turbo-saas-kit

Length of output: 1145


🏁 Script executed:

# Check if these packages are explicitly isomorphic or have entry points
rg -l 'browser|client|react-native' packages/*/package.json packages/features/*/package.json

Repository: makerkit/react-native-expo-turbo-saas-kit

Length of output: 141


🏁 Script executed:

cat -n packages/supabase/src/client.ts | head -50

Repository: makerkit/react-native-expo-turbo-saas-kit

Length of output: 1568


🏁 Script executed:

# Check if supabase package has separate server/client exports
cat -n packages/supabase/package.json | head -30

Repository: makerkit/react-native-expo-turbo-saas-kit

Length of output: 1176


Remove global Node typings from shared base config.

The base config at line 20 includes "node" types by default for all packages that inherit from it. This is unnecessary—while the supabase package uses process.env, it already declares @types/node as a devDependency, indicating types should be scoped locally. Shared packages (ui, auth, account, supabase) should opt-in to Node types explicitly rather than inheriting them through the base config.

Proposed fix
-    "types": ["nativewind/types", "node"]
+    "types": ["nativewind/types"]

Projects inheriting this base—packages/ui, packages/features/auth, packages/features/account, and packages/supabase—should add "node" to their compilerOptions.types locally if needed.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"types": ["nativewind/types", "node"]
"types": ["nativewind/types"]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tooling/typescript-config/base.json` at line 20, Remove the global "node"
typing from the base compilerOptions.types in
tooling/typescript-config/base.json so the base no longer forces Node types on
all packages; update the "types" array (currently ["nativewind/types", "node"])
to omit "node", and add a note or document that packages which require Node
typings (e.g., the supabase package that uses process.env) should explicitly
opt-in by adding "node" to their own compilerOptions.types in their local
tsconfig (packages/ui, packages/features/auth, packages/features/account,
packages/supabase as needed).

@Gbuomprisco Gbuomprisco merged commit 74a9b57 into makerkit:main May 21, 2026
4 checks passed
@Gbuomprisco Gbuomprisco mentioned this pull request May 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants